package cn.xzqwjw.taskmanager.common.exception;

import cn.hutool.core.util.ArrayUtil;
import cn.xzqwjw.taskmanager.domain.vo.ResponseVo;
import cn.xzqwjw.taskmanager.common.customEnum.ResponseCodeEnum;
import cn.xzqwjw.taskmanager.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.security.sasl.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 定义统一的异常处理类，而不是在每个Controller中逐个定义
 * 注意：无法处理 filter 抛出的异常
 *
 * @author wjw
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

  private static StringBuilder sb;

  // ==================== 捕捉自定义异常 ====================

  /**
   * 处理捕捉到的自定义异常
   */
  @ExceptionHandler(CustomException.class)
  public String handleCustomException(CustomException ex) {
    log.error("=====> 捕捉到自定义的异常：{}", ex.getMessage());
    if (StringUtils.hasLength(ex.getStatusCode())) {
      return JsonUtil.obj2Json(ResponseVo.error(ex.getStatusCode(), ex.getMessage()));
    } else {
      return JsonUtil.obj2Json(ResponseVo.error(ex.getMessage()));
    }
  }

  // ==================== 捕捉java内设异常 ====================

  /**
   * 鉴权异常
   */
  @ExceptionHandler(AuthenticationException.class)
  public String handleAuthenticationException(AuthenticationException ex) {
    log.error("=====> 鉴权异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.FORBIDDEN, ex);
  }

  /**
   * 运行时异常
   */
  @ExceptionHandler(RuntimeException.class)
  public String runtimeExceptionHandler(RuntimeException ex) {
    log.error("=====> 运行时异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 空指针异常
   */
  @ExceptionHandler(NullPointerException.class)
  public String nullPointerExceptionHandler(NullPointerException ex) {
    log.error("=====> 空指针异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 类型转换异常
   */
  @ExceptionHandler(ClassCastException.class)
  public String classCastExceptionHandler(ClassCastException ex) {
    log.error("=====> 类转换异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * IO异常
   */
  @ExceptionHandler(IOException.class)
  public String ioExceptionHandler(IOException ex) {
    log.error("=====> IOException异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 未知方法异常
   */
  @ExceptionHandler(NoSuchMethodException.class)
  public String noSuchMethodExceptionHandler(NoSuchMethodException ex) {
    log.error("=====> 没有找到对应方法异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 数组越界异常
   */
  @ExceptionHandler(IndexOutOfBoundsException.class)
  public String indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
    log.error("=====> 索引下标越界异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * http信息不可读
   */
  @ExceptionHandler({HttpMessageNotReadableException.class})
  public String requestNotReadableHandler(HttpMessageNotReadableException ex) {
    log.error("=====> http信息不可读异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 类型不匹配
   */
  @ExceptionHandler({TypeMismatchException.class})
  public String requestTypeMismatchHandler(TypeMismatchException ex) {
    log.error("=====> 类型不匹配异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 媒体类型不支持
   */
  @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
  public String mediaTypeNotAcceptableHandler(HttpMediaTypeNotAcceptableException ex) {
    log.error("=====> 媒体类型不支持异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 转换不支持异常
   */
  @ExceptionHandler({ConversionNotSupportedException.class})
  public String conversionNotSupportedHandler(ConversionNotSupportedException ex) {
    log.error("=====> 转换不支持异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * http信息不可写异常
   */
  @ExceptionHandler({HttpMessageNotWritableException.class})
  public String messageNotWritableHandler(HttpMessageNotWritableException ex) {
    log.error("=====> http信息不可写异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 栈溢出
   */
  @ExceptionHandler({StackOverflowError.class})
  public String requestStackOverflow(StackOverflowError ex) {
    log.error("=====> 堆栈溢出错误：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 数学计算
   */
  @ExceptionHandler({ArithmeticException.class})
  public String arithmeticException(ArithmeticException ex) {
    log.error("=====> 数学计算异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 不合法参数异常错误
   */
  @ExceptionHandler({IllegalArgumentException.class})
  public String illegalArgumentException(IllegalArgumentException ex) {
    log.error("=====> 不合法参数异常：{}", ex.getMessage());
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, ex);
  }

  /**
   * 处理捕捉到的校验异常 ValidationException
   */
  @ExceptionHandler(ValidationException.class)
  public String handleValidationException(ValidationException ex) {
    String errorMessage = "";
    if (ex instanceof ConstraintViolationException) {
      ConstraintViolationException exs = (ConstraintViolationException) ex;
      Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
      sb = new StringBuilder();
      for (ConstraintViolation<?> cv : violations) {
        sb.append(cv.getMessage()).append(",");
      }
      errorMessage = sb.toString();
      // 去除最后一个","字符
      if (errorMessage.length() > 1) {
        errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
      }
    }
    return formatResult(ResponseCodeEnum.INTERNAL_SERVER_ERROR, errorMessage);
  }

  /* ValidationException一般包括下面3种异常
    ConstraintViolationException（约束冲突异常）
    如实体类中的@Size注解配置和数据库中该字段的长度不统一等问题
    MethodArgumentNotValidException（方法参数校验异常、Bean 校验异常）
    BindException（参数绑定异常）
  */

  /**
   * 处理捕捉到的 ConstraintViolationException 异常
   * 此异常是约束冲突异常，比如pojo里设置的max=10，输入参数长度为11，则抛出此异常
   */
  @ExceptionHandler(ConstraintViolationException.class)
  public String handleConstraintValidationException(HttpServletRequest request,
                                                    ConstraintViolationException ex) {
    log.error("=====> 方法参数校验异常:" + request.getRequestURI(), ex);
    String errorMessage = ex.getConstraintViolations().stream().filter(Objects::nonNull)
        .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
        .collect(Collectors.joining(", "));
    return formatResult(ResponseCodeEnum.BAD_REQUEST, errorMessage);
  }

  /**
   * 方法参数校验异常
   */
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public String methodArgumentValidationHandler(HttpServletRequest request,
                                                MethodArgumentNotValidException ex) {
    log.error("=====> 方法参数校验异常:" + request.getRequestURI(), ex);
    log.info(
        "{} - {}",
        "请求参数错误！" + getExceptionDetail(ex),
        "参数数据：" + showParams(request)
    );
    String errorMessage;
    if (!CollectionUtils.isEmpty(ex.getBindingResult().getAllErrors())) {
      errorMessage = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
    } else {
      errorMessage = ex.getMessage();
    }
    return formatResult(ResponseCodeEnum.BAD_REQUEST, errorMessage);
  }

  /**
   * 绑定异常
   */
  @ExceptionHandler(BindException.class)
  public String bindException(HttpServletRequest request, BindException ex) {
    log.error("=====> 绑定异常:" + request.getRequestURI(), ex);
    Map<String, String> map = new HashMap<>(16);
    List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
    allErrors.stream()
        .filter(Objects::nonNull)
        .forEach(objectError ->
            map.put(
                "请求路径：" + request.getRequestURI() +
                    "--请求参数：" + ((FieldError) (allErrors.get(0))).getField(),
                objectError.getDefaultMessage()
            )
        );
    return formatResult(ResponseCodeEnum.BAD_REQUEST, map.toString());
  }

  /**
   * 访问接口参数不全
   */
  @ExceptionHandler(MissingServletRequestParameterException.class)
  public String missingServletRequestParameterException(HttpServletRequest request,
                                                        MissingServletRequestParameterException ex) {
    log.error("=====> 访问接口参数不全异常:" + request.getRequestURI(), ex);
    String errorMessage = "请求路径：" + request.getRequestURI() +
        "的请求参数不全：" + ex.getMessage();
    return formatResult(ResponseCodeEnum.BAD_REQUEST, errorMessage);
  }

  /**
   * 不支持的http请求方式
   */
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  public String httpMethodNotSupportedException(HttpServletRequest request,
                                                HttpRequestMethodNotSupportedException ex) {
    log.error("=====> 不支持的http请求方式异常:" + request.getRequestURI(), ex);
    return formatResult(ResponseCodeEnum.BAD_REQUEST, ex.getMessage());
  }

  /**
   * 其他异常
   */
  @ExceptionHandler(Exception.class)
  public String otherException(HttpServletRequest request, Exception ex) {
    log.error("=====> 其他异常:" + request.getRequestURI(), ex);
    return formatResult(ResponseCodeEnum.BAD_REQUEST, ex.getMessage());
  }

  // ==================== 用到的私有方法 ====================

  /**
   * 获取异常详情
   */
  private String getExceptionDetail(Exception e) {
    StringBuilder stringBuffer = new StringBuilder(e.toString() + "\n");
    StackTraceElement[] messages = e.getStackTrace();
    Arrays.stream(messages)
        .filter(Objects::nonNull)
        .forEach(stackTraceElement -> stringBuffer.append(stackTraceElement).append("\n"));
    return stringBuffer.toString();
  }

  /**
   * 获取请求参数
   */
  private String showParams(HttpServletRequest request) {
    sb = new StringBuilder();
    Enumeration<String> paramNames = request.getParameterNames();
    sb.append("----------------参数开始-------------------");
    sb.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    if (Objects.nonNull(paramNames)) {
      while (paramNames.hasMoreElements()) {
        String paramName = paramNames.nextElement();
        String[] paramValues = request.getParameterValues(paramName);
        if (!ArrayUtil.isEmpty(paramValues)) {
          String paramValue = paramValues[0];
          if (!paramValue.isEmpty()) {
            sb.append("参数名:").append(paramName).append(" 参数值:").append(paramValue);
          }
        }
      }
    }
    sb.append("----------------参数结束-------------------");
    return sb.toString();
  }

  /**
   * 格式化异常输出
   */
  private <T extends Throwable> String formatResult(ResponseCodeEnum responseCodeEnum, T ex) {
    // e.printStackTrace() 要尽量少用，可能会导致锁死
    log.error("=====> 发生异常，ex 为：", ex);
    return JsonUtil.obj2Json(ResponseVo.error(responseCodeEnum.getStatusCode(), ex.getMessage()));
  }

  /**
   * 格式化异常输出
   */
  private String formatResult(ResponseCodeEnum responseCodeEnum, String exMessage) {
    return JsonUtil.obj2Json(ResponseVo.error(responseCodeEnum.getStatusCode(), exMessage));
  }

}
